package library.http.ssl;

import android.content.Context;
import android.text.TextUtils;
import android.util.Log;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;

import library.http.HttpStatic;
import okhttp3.OkHttpClient;

/**
 * Created by HKYS on 2016-9-2.
 */

public class SslUtils {
    private static final String TAG = SslUtils.class.getSimpleName();

    public static void configure(OkHttpClient.Builder builder, SslConfig sslConfig) {
        SSLSocketFactory sslSocketFactory = getSocketFactory(sslConfig);
        if (sslSocketFactory != null)
            builder.sslSocketFactory(sslSocketFactory);
        else {
            Log.e(TAG, "sslSocketFactory failed");
        }
    }

    public static SSLSocketFactory getSocketFactory(SslConfig sslConfig) {
        try {
            String sslType = sslConfig.getSslType();
            KeyManager[] keyManagers = loadKeyManagers(sslConfig);
            TrustManager[] trustManagers = loadTrustManagers(sslConfig);
            String protocol = !TextUtils.isDigitsOnly(sslType) ? sslType : "TLS";
            SSLContext sslContext = SSLContext.getInstance(protocol);
            sslContext.init(keyManagers, trustManagers, new SecureRandom());
            SSLSocketFactory socketFactory = sslContext.getSocketFactory();
            return socketFactory;
        } catch (Exception ignore) {

        }
        return null;
    }

    private static KeyManager[] loadKeyManagers(SslConfig sslConfig) {
        if (sslConfig == null)
            return null;
        try {
            Object res = sslConfig.getSslKeySrc();
            String keyType = sslConfig.getSslKeyType();
            String managerType = sslConfig.getSslKeyManagerType();
            char[] pwd = !TextUtils.isEmpty(sslConfig.getSslKeyPassword()) ? sslConfig.getSslKeyPassword().toCharArray() : null;

            InputStream inputStream = getInputStream(HttpStatic.getContext(), res);
            if (inputStream == null)
                return null;
            KeyStore keyStore;
            try {
                keyStore = KeyStore.getInstance(keyType);
                keyStore.load(inputStream, pwd);
            } catch (Exception e) {
                Log.d(TAG, String.format("Load Key Store Failed (%s:%s):%s", keyType, res, e.getMessage()));
                throw e;
            } finally {
                try {
                    inputStream.close();
                } catch (Exception ignore) {
                }
            }

            KeyManager[] managers;

            Enumeration<?> enumeration = keyStore.aliases();
            List<AliasKeyManager> list = new ArrayList<>();
            while (enumeration.hasMoreElements()) {
                String alias = (String) enumeration.nextElement();
                AliasKeyManager aliasKeyManager = new AliasKeyManager(keyStore, alias, pwd);
                list.add(aliasKeyManager);
            }
            if (list.size() > 0) {
                managers = list.toArray(new AliasKeyManager[list.size()]);
            } else {
                KeyManagerFactory factory = KeyManagerFactory.getInstance(!TextUtils.isEmpty(managerType) ? managerType : KeyManagerFactory.getDefaultAlgorithm());
                factory.init(keyStore, pwd);
                managers = factory.getKeyManagers();
            }
            return managers;
        } catch (Exception e) {
//            HttpUtils.debug(e);
        }
        return null;
    }

    private static TrustManager[] loadTrustManagers(SslConfig sslConfig) {
        if (sslConfig != null) {
            try {
                Object res = sslConfig.getSslTrustSrc();
                String keyType = sslConfig.getSslTrustType();
                String managerType = sslConfig.getSslTrustManagerType();
                char[] pwd = !TextUtils.isEmpty(sslConfig.getSslTrustPassword()) ? sslConfig.getSslTrustPassword().toCharArray() : null;

                InputStream inputStream = getInputStream(HttpStatic.getContext(), res);
                KeyStore trustStore;
                try {
                    trustStore = KeyStore.getInstance(keyType);
                    trustStore.load(inputStream, pwd);
                } catch (Exception e) {
                    Log.e(TAG, String.format("Load Trust Store Failed (%s:%s):%s", keyType, res, e.getMessage()));
                    throw e;
                } finally {
                    try {
                        inputStream.close();
                    } catch (Exception ignore) {
                    }
                }
                TrustManagerFactory factory = TrustManagerFactory.getInstance(!TextUtils.isEmpty(managerType) ? managerType : KeyManagerFactory.getDefaultAlgorithm());
                factory.init(trustStore);
                TrustManager[] managers = factory.getTrustManagers();
                if (managers != null)
                    return managers;
            } catch (Exception e) {
//                HttpUtils.debug(e);
            }
        }
        return new TrustManager[]{
                new X509TrustManager() {
                    @Override
                    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {

                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {

                    }

                    @Override
                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[0];
                    }
                }
        };
    }


    public static InputStream getInputStream(Context context, Object obj) {
        InputStream is = null;
        try {
            if (obj instanceof InputStream)
                is = (InputStream) obj;
            else if (obj instanceof String) {
                String path = (String) obj;
                if (!path.startsWith("/")) {
                    if (context != null)
                        try {
                            is = context.getAssets().open(path);
                        } catch (Exception e) {

                        }
                }
                if (is == null) {
                    try {
                        is = new FileInputStream(path);
                    } catch (Exception e) {

                    }
                }
                if (is == null) {
                    is = new ByteArrayInputStream(path.getBytes());
                }
            } else if (obj instanceof Integer) {
                int id = (Integer) obj;
                if (context != null)
                    is = context.getResources().openRawResource(id);
            }
        } catch (Exception ignore) {

        }
        return is;
    }
}
